package a5.common;

import java.util.List;

import a5.environment.Environment;

/**
 * <pre>
 * A cursor for iterating over and performing assertions on a list of {@link a5.common.Token Tokens}.
 * Several functions are available for pointer manipulation within the list of token to allow non-linear
 * program execution if desired. 
 * 
 * Additionally, the {@link #match(String)} and {@link #first(String)} methods are available for token
 * type checking and assertions. 
 * </pre>
 * @author Jason Campos
 * @version Assignment 5, CS 152 (Programming Paradigms) Fall 2012, Jeffrey Smith
 */
public class ProgramCursor {
	/** Pointer to the token representing the point of program execution. */
	private int currentToken;
	/** The full set of program tokens */
	private List<Token> tokens;
	
	/**
	 * Initializes a curor to the argument set of {@link a5.common.Token Tokens}. 
	 * @param tokens
	 * The tokens over which to iterate and perform matching operations.
	 */
	public ProgramCursor(List <Token> tokens){
		this.currentToken = 0;
		this.tokens = tokens;
	}
	
	/**
	 * Performs an assertion on the token type of the current {@code Token} and 
	 * advances the pointer of the current {@code Token}.
	 * 
	 * @param expected
	 * The expected type of the current {@code Token}.
	 * 
	 * @return
	 * The spelling of the current {@code Token}. 
	 * 
	 * @throws IllegalArgumentException
	 * Thrown when the assertion on the token type fails. In other words, the argument expected does not match the token 
	 * type of the current {@code Token}.	
	 */
	public final String match(String expected) throws IllegalArgumentException{
		// Assert the expected token type.
		// Then, advance to the next token and return the current token's spelling
		Token currToken = currentToken();
		String spelling = currToken.getSpelling();
    	if(!currToken.getType().equals(expected))
    		throw new IllegalArgumentException(spelling + " found, " + expected + " expected");
		
		advance();
		return spelling;
	}
	
	/**
	 * Performs an assertion on the token type of the current {@code Token} and 
	 * advances the pointer of the current {@code Token}.
	 * @param env 
	 * The environment containing all variables and functions currently in scope.
	 * 
	 * @param expected
	 * The expected type of the current {@code Token}.
	 * 
	 * @return
	 * The spelling of the current {@code Token}. 
	 * 
	 * @throws IllegalArgumentException
	 * Thrown when the assertion on the token type fails. In other words, the argument expected does not match the token 
	 * type of the current {@code Token}.	
	 */
	public final String match(Environment env, String expected) throws IllegalArgumentException{
		// Assert the expected token type.
		// Then, advance to the next token and return the current token's spelling
		Token currToken = currentToken();
		String spelling = currToken.getSpelling();
    	if(!currToken.getType().equals(expected))
    		throw new IllegalArgumentException(spelling + " found, " + expected + " expected in " + env);

		advance();
		return spelling;
	}
	
	/**
	 * Checks whether the current {@code Token} is of the argument type.
	 * 
	 * @param expected The expected type of the current {@code Token}
	 * @return {@code true} if the current {@code Token}'s type matches the argument. {@code false} otherwise
	 */
	public final boolean first(String expected){
		return expected.equals(currentToken().getType());
	}
	
	/**
	 * @return
	 * The token currently being pointed to by {@link #currentToken}. 
	 * @throws IllegalArgumentException
	 * Thrown if {@link #currentToken} references an invalid position within {@link #tokens}
	 */
	private Token currentToken(){
		if(currentToken < 0 || currentToken >= tokens.size())
			throw new IllegalArgumentException("Requested token location does not exist. Requested token: " + currentToken);
		
		return tokens.get(currentToken);
	}
	
	/**
	 * Advances the {@link #currentToken()} to the next position in the program.
	 */
	public void advance(){
		currentToken++;
	}

	/**
	 * Alters the position of {@link #currentToken}. 
	 * @param location
	 * The desired {@link #currentToken}
	 * @throws IllegalArgumentException
	 * Thrown when the argument does not lie within bounds of the program.
	 */
	public void seek(int location) {
		if(location < 0 || location >= tokens.size())
			throw new IllegalArgumentException("Invalid location: " + location);
		
		currentToken = location;
	}
	
	/**
	 * @return
	 * The location of the current token within the program.
	 */
	public int getLocation() {
		return currentToken;
	}
}
